-- Profiles.lua — profils DFFT (sauvegarde/chargement/mapping par perso, DB dynamique)
local ADDON, DFFT = ...
if not DFFT then return end

-- ==== always use the live DB table ====
local function DB()
  if type(DFFT.DB) ~= "table" then DFFT.DB = {} end
  return DFFT.DB
end

-- ========= utils =========
local function deepcopy(src)
  if type(src) ~= "table" then return src end
  local t = {}
  for k,v in pairs(src) do t[k] = deepcopy(v) end
  return t
end

local function trim(s) return (tostring(s or ""):gsub("^%s+",""):gsub("%s+$","")) end

local function nowISO()
  if date then return date("!%Y-%m-%d %H:%M:%S") end
  return "未知"
end

local function charKey()
  local name  = UnitName and UnitName("player") or "未知"
  local realm = GetNormalizedRealmName and GetNormalizedRealmName() or GetRealmName and GetRealmName() or "服务器"
  return tostring(name) .. "-" .. tostring(realm)
end

-- clés à NE PAS inclure dans les profils (méta internes)
local RESERVED_KEYS = {
  _profiles = true,
  _charToProfile = true,
  _lastAppliedProfile = true,
  _fontBootstrapped = true,
}

-- fingerprint (top-level non réservés)
local function fingerprintTop(tbl)
  local n, first = 0, nil
  for k,_ in pairs(tbl or {}) do
    if not RESERVED_KEYS[k] then
      n = n + 1
      if not first then first = tostring(k) end
    end
  end
  return n, first
end

-- snapshot « propre » (sans clés réservées)
local function snapshotDB()
  local src = DB()
  local snap = {}
  for k,v in pairs(src) do
    if not RESERVED_KEYS[k] then
      snap[k] = deepcopy(v)
    end
  end
  return snap
end

-- merge “soft” (complète uniquement les manquants)
local function fillMissing(dst, defs)
  if type(dst) ~= "table" or type(defs) ~= "table" then return end
  for k,v in pairs(defs) do
    if dst[k] == nil then
      dst[k] = deepcopy(v)
    elseif type(dst[k]) == "table" and type(v) == "table" then
      fillMissing(dst[k], v)
    end
  end
end

-- remplace la DB courante par un profil (sans écraser les réservés)
local function replaceDBWith(src)
  local db = DB()

  -- garder réservés
  local keep_profiles    = db._profiles
  local keep_charmap     = db._charToProfile
  local keep_lastApplied = db._lastAppliedProfile
  local keep_fontBoot    = db._fontBootstrapped

  -- wipe des non-réservés
  for k in pairs(db) do
    if not RESERVED_KEYS[k] then db[k] = nil end
  end

  -- injecter le contenu du profil
  for k,v in pairs(src or {}) do
    if not RESERVED_KEYS[k] then
      db[k] = deepcopy(v)
    end
  end

  -- remettre réservés
  db._profiles           = keep_profiles or db._profiles or {}
  db._charToProfile      = keep_charmap  or db._charToProfile or {}
  db._lastAppliedProfile = keep_lastApplied
  db._fontBootstrapped   = keep_fontBoot
end

-- notifier l’engine que la config a changé
local function NotifyAllChanged(kind)
  local db = DB()
  if DFFT then
    -- Healer side
    if DFFT.Healer then
      if type(DFFT.Healer.RefreshPalette)=="function" then pcall(DFFT.Healer.RefreshPalette) end
      if type(DFFT.Healer.RebuildColors)=="function"  then pcall(DFFT.Healer.RebuildColors)  end
      if type(DFFT.Healer.ApplyTheme)=="function" and db.healTheme then
        pcall(DFFT.Healer.ApplyTheme, db.healTheme)
      end
      if type(DFFT.Healer.SetAbsorbsEnabled)=="function" and db.healer and db.healer.absorbs then
        pcall(DFFT.Healer.SetAbsorbsEnabled, not not (db.healer.absorbs.show or db.healer.absorbsEnabled or db.showAbsorbs))
      end
    end

    if type(DFFT.refreshActiveFonts)=="function" then pcall(DFFT.refreshActiveFonts) end
    if type(DFFT.RefreshPalette)=="function"     then pcall(DFFT.RefreshPalette)     end
    if type(DFFT.RebuildColors)=="function"      then pcall(DFFT.RebuildColors)      end

    if type(DFFT.ApplyDamageTheme)=="function" and db.damageTheme then
      pcall(DFFT.ApplyDamageTheme, db.damageTheme)
    end

    if type(DFFT.ApplyDefaultCombatTextSetting)=="function" then
      pcall(DFFT.ApplyDefaultCombatTextSetting)
    end

    if type(DFFT.SetBigCritShadowEnabled)=="function" and db.bigCritShadowEnabled ~= nil then
      pcall(DFFT.SetBigCritShadowEnabled, not not db.bigCritShadowEnabled)
    end

    if type(DFFT.OnConfigChanged)=="function" then pcall(DFFT.OnConfigChanged, kind or "profile") end
  end
end

-- ========= API publique =========
DFFT.Profile = DFFT.Profile or {}

function DFFT.Profile.List()
  local t, p = {}, DB()._profiles or {}
  for name in pairs(p) do table.insert(t, name) end
  table.sort(t, function(a,b) return tostring(a):lower() < tostring(b):lower() end)
  return t
end

function DFFT.Profile.Exists(name)
  name = trim(name)
  return (DB()._profiles and DB()._profiles[name]) and true or false
end

function DFFT.Profile.Save(name)
  name = trim(name)
  if name == "" then return false, "名称为空" end

  local db = DB()
  db._profiles = db._profiles or {}

  local snap = snapshotDB()
  db._profiles[name] = snap
  db._profiles[name]._meta = { savedAt = nowISO(), version = DFFT.version or "" }

  db._lastAppliedProfile = name -- utile pour login suivant si même perso

  local n, first = fingerprintTop(snap)
  if first then
    print(string.format("|cffffd100DFFT:|r 配置文件已保存: %s (键数=%d {%s=...})", name, n, first))
  else
    print(string.format("|cffffd100DFFT:|r 配置文件已保存: %s (键数=%d {})", name, n))
  end
  return true
end

-- load + (optionnel) activer pour ce perso
function DFFT.Profile.Load(name, makeActiveForThisChar)
  name = trim(name)
  local db = DB()
  if name == "" or not db._profiles or not db._profiles[name] then
    return false, "未找到配置文件"
  end

  local snap = db._profiles[name]
  if type(snap)=="table" and type(snap._meta)=="table" then
    snap = deepcopy(snap); snap._meta = nil
  end

  replaceDBWith(snap)

  -- complète seulement les manquants depuis defaults (sans écraser le profil)
  if DFFT and type(DFFT.defaults)=="table" then
    fillMissing(DB(), DFFT.defaults)
  end

  DB()._lastAppliedProfile = name

  if makeActiveForThisChar then
    DFFT.Profile.SetActiveForChar(name)
  end

  NotifyAllChanged("profileLoad")

  local n, first = fingerprintTop(DB())
  if first then
    print(string.format("|cffffd100DFFT:|r 配置文件已加载: %s (键数=%d {%s=...})", name, n, first))
  else
    print(string.format("|cffffd100DFFT:|r 配置文件已加载: %s (键数=%d {})", name, n))
  end
  return true
end

function DFFT.Profile.Delete(name)
  name = trim(name)
  local db = DB()
  if name == "" or not db._profiles or not db._profiles[name] then
    return false, "未找到配置文件"
  end
  db._profiles[name] = nil
  local map = db._charToProfile or {}
  for ck, pn in pairs(map) do if pn == name then map[ck] = nil end end
  print("|cffffd100DFFT:|r 配置文件已删除:", name)
  return true
end

function DFFT.Profile.Rename(oldName, newName)
  oldName = trim(oldName); newName = trim(newName)
  local db = DB()
  if oldName=="" or newName=="" then return false, "无效名称" end
  if not db._profiles or not db._profiles[oldName] then return false, "旧名称不存在" end
  if db._profiles[newName] then return false, "新名称已存在" end
  db._profiles[newName] = db._profiles[oldName]; db._profiles[oldName] = nil
  local map = db._charToProfile or {}
  for ck, pn in pairs(map) do if pn == oldName then map[ck] = newName end end
  if db._lastAppliedProfile == oldName then db._lastAppliedProfile = newName end
  print("|cffffd100DFFT:|r 配置文件已重命名:", oldName, "->", newName)
  return true
end

function DFFT.Profile.GetActiveForChar()
  local db = DB()
  db._charToProfile = db._charToProfile or {}
  local ck = charKey()
  return db._charToProfile[ck]
end

function DFFT.Profile.SetActiveForChar(name)
  name = trim(name)
  local db = DB()
  if name ~= "" and (not db._profiles or not db._profiles[name]) then
    return false, "未找到配置文件"
  end
  db._charToProfile = db._charToProfile or {}
  db._charToProfile[charKey()] = (name ~= "" and name) or nil
  print("|cffffd100DFFT:|r 此角色的活动配置文件:", db._charToProfile[charKey()] or "<无>")
  return true
end

-- auto-apply sur login si un mapping existe
local ev = CreateFrame("Frame")
ev:RegisterEvent("PLAYER_LOGIN")
ev:SetScript("OnEvent", function()
  local want = DFFT.Profile.GetActiveForChar()
  if not want or want == "" then return end
  if DB()._lastAppliedProfile == want then return end
  if DB()._profiles and DB()._profiles[want] then
    DFFT.Profile.Load(want, false)
  end
end)
